package easycloud.ecs.agent.service;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.inject.Inject;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.stereotype.Service;

import easycloud.ecs.agent.Agent;
import easycloud.ecs.common.domain.Hash;
import easycloud.ecs.common.domain.Item;
import easycloud.ecs.common.domain.Item.ITEM_STATUS;
import easycloud.ecs.common.domain.Provider;
import easycloud.ecs.common.domain.Provider.PROVIDER_TYPE;
import easycloud.ecs.common.domain.Sync;
import easycloud.ecs.common.exception.ConfigException;
import easycloud.ecs.common.service.ItemService;
import easycloud.ecs.provider.api.IProvider;
import easycloud.ecs.provider.api.ProviderException;
import easycloud.ecs.provider.api.local.LocalProvider;
import easycloud.ecs.provider.model.IStorageObjectMetadata;
import easycloud.ecs.sync.Knowledge;

@Service
public class KnowledgeService {

	private static final Log logger = LogFactory.getLog(KnowledgeService.class);

	@Inject
	private Agent agent;

	@Inject
	private ItemService itemService;

	public void merge(Knowledge know, Map<String, Item> remoteItems) {
		Map<String, IStorageObjectMetadata> localMetas = know.getMetadatas();
		for (Entry<String, Item> entry : remoteItems.entrySet()) {
			String itemPath = entry.getKey();
			IStorageObjectMetadata remoteMeta = entry.getValue();
			if (localMetas.containsKey(itemPath)) {
				IStorageObjectMetadata localMeta = localMetas.get(itemPath);
				if (localMeta.getItem().getUpdateTick() > remoteMeta.getItem()
						.getUpdateTick()) {
					localMeta.getItem().setStatus(ITEM_STATUS.LOCAL_UPDATE);
				} else {
					if (localMeta.getItem().getStatus() == ITEM_STATUS.LOCAL_UPDATE) {
						localMeta.getItem().setStatus(ITEM_STATUS.CONFLICT);
					}
				}
			} else {
				know.getMetadatas().put(itemPath, remoteMeta.getItem());
			}
		}
	}

	public Knowledge getLocalKnowledge() throws ProviderException {
		Map<String, IStorageObjectMetadata> localMetas = readLocalMeta();
		Map<String, IStorageObjectMetadata> scanMetas = scanLocalItems();
		Knowledge know = new Knowledge();

		for (Entry<String, IStorageObjectMetadata> entry : localMetas
				.entrySet()) {
			String itemPath = entry.getKey();
			IStorageObjectMetadata localMeta = entry.getValue();
			if (scanMetas.containsKey(itemPath)) {
				IStorageObjectMetadata scanMeta = scanMetas.get(itemPath);
				if (!scanMeta.getHash(agent.getProvider()).equals(
						localMeta.getHash(agent.getProvider()))) {
					// Update
					scanMeta.getItem().setStatus(ITEM_STATUS.LOCAL_UPDATE);
					scanMeta.getItem().setUpdateTick(
							scanMeta.getItem().getUpdateTick() + 1);
					scanMeta.getItem().setUpdateProvider(
							this.agent.getProvider());
					know.getMetadatas().put(itemPath, scanMeta);
				} else {
					// Unchange
					know.getMetadatas().put(itemPath, scanMeta);
				}
			} else {
				// Delete
				localMeta.getItem().setStatus(ITEM_STATUS.LOCAL_DELETE);
				localMeta.getItem().setUpdateProvider(agent.getProvider());
				localMeta.getItem().setUpdateTick(
						localMeta.getItem().getUpdateTick() + 1);
				know.getMetadatas().put(itemPath, localMeta);
			}
		}

		for (Entry<String, IStorageObjectMetadata> entry : scanMetas.entrySet()) {
			if (entry.getValue().getItem().getStatus() == null) {
				Item localNewItem = entry.getValue().getItem();
				Hash localNewHash = new Hash();
				localNewHash.setHash(entry.getValue().getHash(
						agent.getProvider()));
				localNewHash.setProvider(agent.getProvider());
				localNewHash.setItem(localNewItem);
				localNewItem.getHashs().add(localNewHash);
				localNewItem.setLength(entry.getValue().getLength());
				localNewItem.setStatus(ITEM_STATUS.LOCAL_NEW);
				localNewItem.setCreateTick(1);
				localNewItem.setCreateProvider(agent.getProvider());
				localNewItem.setUpdateTick(1);
				localNewItem.setUpdateProvider(agent.getProvider());
				know.getMetadatas().put(entry.getKey(), entry.getValue());
			}
		}

		return know;
	}

	@SuppressWarnings("unchecked")
	public Map<String, IStorageObjectMetadata> getLocalMetadata(File file)
			throws ConfigException {
		Map<String, IStorageObjectMetadata> items = new HashMap<String, IStorageObjectMetadata>();
		if (file.exists()) {
			ObjectMapper objectMapper = new ObjectMapper();
			JsonParser parser;
			try {
				parser = objectMapper.getJsonFactory().createJsonParser(file);
				items = parser.readValueAs(Map.class);
			} catch (JsonParseException e) {
				KnowledgeService.logger.warn("Parse items failed", e);
				throw new ConfigException("Parse items failed");
			} catch (IOException e) {
				KnowledgeService.logger.warn("Read items failed", e);
				throw new ConfigException("Read items failed");
			}
		}
		return items;
	}

	public Map<String, Item> getRemoteMetadata(Provider provider) {
		Map<String, Item> remoteItems = itemService.getItems(provider);
		return remoteItems;
	}

	public Map<String, IStorageObjectMetadata> readLocalMeta() {
		File itemsFile = new File(FileUtils.getUserDirectory(),
				"agent.metadata.json");
		Map<String, IStorageObjectMetadata> items = new HashMap<String, IStorageObjectMetadata>();
		try {
			items = getLocalMetadata(itemsFile);
		} catch (ConfigException e) {
			e.printStackTrace();
		}
		return items;
	}

	public Map<String, IStorageObjectMetadata> scanLocalItems()
			throws ProviderException {
		Map<String, IStorageObjectMetadata> items = new HashMap<String, IStorageObjectMetadata>();
		for (Sync sync : agent.getProvider().getSyncs()) {
			IProvider providerImpl = agent.getProivderImpl();
			List<IStorageObjectMetadata> listing = providerImpl.list(sync
					.getFolder());
			for (IStorageObjectMetadata metadata : listing) {
				items.put(metadata.getPath(), metadata);
			}
		}
		return items;
	}

	public void writeLocalMetadata(Map<String, IStorageObjectMetadata> items,
			File file) throws ConfigException {
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				KnowledgeService.logger.warn("Create items failed", e);
				throw new ConfigException("Create items failed");
			}
			ObjectMapper objectMapper = new ObjectMapper();
			String value;
			try {
				value = objectMapper.writeValueAsString(items);
				FileUtils.write(file, value);
			} catch (JsonGenerationException e) {
				KnowledgeService.logger.warn("Parse monitor config failed", e);
				throw new ConfigException("Parse monitor config failed");
			} catch (JsonMappingException e) {
				KnowledgeService.logger.warn("Parse monitor config failed", e);
				throw new ConfigException("Parse monitor config failed");
			} catch (IOException e) {
				KnowledgeService.logger.warn("Save monitor config failed", e);
				throw new ConfigException("Save monitor config failed");
			}
		}
	}

	public void updateServer(List<IStorageObjectMetadata> localItems) {
		this.itemService.updateServer(localItems);
	}

	public void updateConflict(List<IStorageObjectMetadata> conflictItems) {
		this.itemService.updateConflict(conflictItems);
	}

	public void updateLocal(List<IStorageObjectMetadata> remoteItems)
			throws ProviderException, IOException {
		IProvider localProvider = this.agent.getProivderImpl();
		for (IStorageObjectMetadata meta : remoteItems) {
			if (meta.getItem().getStatus() == ITEM_STATUS.REMOTE_DELETE) {
				localProvider.delete(meta.getItem());
			} else if (meta.getItem().getStatus() == ITEM_STATUS.REMOTE_UPDATE
					|| meta.getItem().getStatus() == ITEM_STATUS.REMOTE_NEW) {
				Provider remoteProvider = meta.getItem().getUpdateProvider();
				URL source = itemService.getAccessURL(remoteProvider,
						meta.getItem());
				if (agent.getProvider().getProviderType() == PROVIDER_TYPE.LOCAL) {
					File destination = new File(
							((LocalProvider) localProvider)
									.getPhysicalPath(meta.getItem()));
					FileUtils.copyURLToFile(source, destination);
				}else if(agent.getProvider().getProviderType() == PROVIDER_TYPE.CLOUD){
					localProvider.putItem(meta.getItem(), source.openStream(), meta.getLength());
				}
			}
		}
	}
}
